﻿/*
 * Created by SharpDevelop.
 * User: Corrado Mio
 * Date: 21/06/2012
 * Time: 14:48
 * 
 * La classe mette a disposizione un parse di URL generalizzate.
 * La URL non dipende da un particolare ptocollo, ma e' sufficiente che abbia una
 * sitassi simile a:
 * 
 * 
 *      <url>    ::=  <scheme>:<scheme-specific-part>
 *      <scheme> ::=  <protocol> (:<protocol>)*
 *      <scheme-specific-part> ::= (<user>:<password>@)? <host> (:<port>)? (/<url-path>)? (?<query>)? (;<args>)?
 * 
 * 
 * Ad esempio, e' supportata una sintassi del tipo:
 * 
 * 
 *      udp:pgm://pippo:pippese@127.0.0.1:1234/sotto/directory/resource.xml?encode=ISO8859&uppercase=false
 * 
 */
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using CSharpExt.Util;

namespace CSharpExt.Net
{
    // <url>    ::=  <scheme>:<scheme-specific-part>
    // <scheme> ::=  <protocol> (:<protocol>)*
    // <scheme-specific-part> ::= (<user>:<password>@)? <host> (:<port>)? (/<url-path>)? (?<query>)? (;<args>)?
    // <query>  ::=  <name>=<value>(& ...)*
    //               <name>
    // <args>   ::=  <name>=<value>(, ...)*
    //               <name>
    //
    // i caratteri speciali devono essere convertiti in %XX
    public class URL
    {
        #region Private Data

        private static readonly char[] COLON = { ':' };
        private static readonly char[] SEPARATOR = { '&', ',' };

        private String _url;
        private String[] _protocols;
        private String _user;
        private String _password;
        private String _host;
        private int _port;
        private String _path;
        private Dictionary<String, String> _query = new Dictionary<String, String>();

        #endregion

        #region Constructor

        public URL(String url)
        {
            parseUrl(url);
        }

        #endregion

        #region Properties

        public String Protocol
        {
            get { return _protocols[0]; }
        }

        public String[] Protocols
        {
            get { return _protocols; }
        }

        public bool HasProtocol(String protocol)
        {
            return hasProtocol(protocol);
        }

        public String Host
        {
            get { return _host; }
        }

        public int Port
        {
            get { return _port; }
        }

        public String User
        {
            get { return _user; }
        }

        public String Password
        {
            get { return _password; }
        }

        public String Path
        {
            get { return _path; }
        }

        public Dictionary<String, String> Query
        {
            get { return _query; }
        }

        public String[] Parameters
        {
            get { return _query.Keys.ToArray(); }
        }

        #endregion

        #region Properties/2

        public ProtocolType ProtocolType
        {
            get
            {
                return GetProtocolType(ProtocolType.Udp);
            }
        }

        public ProtocolType GetProtocolType(ProtocolType defaultProtocolType)
        {
            if (HasProtocol("tcp"))
                return ProtocolType.Tcp;
            if (HasProtocol("udp"))
                return ProtocolType.Udp;
            else
                return defaultProtocolType;
        }

        public IPAddress Address
        {
            get
            {
                return IPAddress.Parse(_host);
            }
        }

        public EndPoint EndPoint
        {
            get
            {
                IPAddress address;
                if (IPAddress.TryParse(_host, out address))
                    return new IPEndPoint(address, Port);
                else
                    return new DnsEndPoint(_host, Port);
            }
        }

        public bool IsMulticast
        {
            get
            {
                byte b0 = Address.GetAddressBytes()[0];
                return 224 <= b0 && b0 <= 239;
            }
        }

        #endregion

        #region Operations

        public bool HasProtocolOrParameter(String name)
        {
            return HasProtocol(name) || HasParameter(name);
        }

        // -- Protocol

        public URL AddProtocol(String protocol)
        {
            if (!hasProtocol(protocol))
                addProtocol(protocol);
            return this;
        }

        // -- Parameter

        public String GetParameter(String name)
        {
            String value;
            _query.TryGetValue(name, out value);
            return value;
        }

        public bool HasParameter(String name)
        {
            return _query.ContainsKey(name);
        }

        public bool GetParameter(String name, bool defaultValue)
        {
            String svalue = GetParameter(name);
            return svalue == null ? defaultValue : bool.Parse(svalue);
        }

        public int GetParameter(String name, int defaultValue)
        {
            String svalue = GetParameter(name);
            return svalue == null ? defaultValue : int.Parse(svalue);
        }

        public double GetParameter(String name, double defaultValue)
        {
            String svalue = GetParameter(name);
            return svalue == null ? defaultValue : double.Parse(svalue, CultureInfo.InvariantCulture);
        }

        public String GetParameter(String name, String defaultValue)
        {
            String svalue = GetParameter(name);
            return svalue == null ? defaultValue : svalue;
        }

        #endregion

        #region Implementation

        private void parseUrl(String url)
        {
            _url = url;

            // <scheme>://<scheme-specific-part>
            String scheme, specificPart;
            {
                int pos = url.IndexOf("://");
                if (pos == -1)
                    throw new SyntaxErrorException(String.Format("{0} not in form <protocol>(:<protocol>)*://(<user>:<password>@)?<host>(:<port>)?(/<url-path>)?(?<query>)? (;<args>)", url));
                scheme = url.Substring(0, pos);
                specificPart = url.Substring(pos + 3);
            }

            // <scheme> ::= (<protocol>:)+
            {
                _protocols = scheme.Split(COLON);
            }

            // <scheme-specific-part> ::= (<user>:<password>@)? ...
            {
                int pos = specificPart.IndexOf('@');
                if (pos == -1)
                {
                    _user = String.Empty;
                    _password = String.Empty;
                }
                else
                {
                    String userPassword = specificPart.Substring(0, pos);
                    specificPart = specificPart.Substring(pos + 1);

                    pos = userPassword.IndexOf(':');
                    if (pos == -1)
                    {
                        _user = userPassword;
                        _password = String.Empty;
                    }
                    else
                    {
                        _user = userPassword.Substring(0, pos);
                        _password = userPassword.Substring(pos + 1);
                    }
                }
            }

            // <scheme-specific-part> ::= <host> (:<port>)? / (/<url-path>)? (?<query>)? (;<args>)?
            String hostPort, pathQueryArgs;
            {
                int pos = specificPart.IndexOf('/');
                if (pos != -1)
                {
                    hostPort = specificPart.Substring(0, pos);
                    pathQueryArgs = specificPart.Substring(pos);
                    goto analyzePort;
                }

                pos = specificPart.IndexOf('?');
                if (pos != -1)
                {
                    hostPort = specificPart.Substring(0, pos);
                    pathQueryArgs = specificPart.Substring(pos);
                    goto analyzePort;
                }
                else
                {
                    hostPort = specificPart;
                    pathQueryArgs = String.Empty;
                }
            }
            analyzePort:

            // <host> (:<port>)?
            {
                int pos = hostPort.IndexOf(':');

                if (pos == -1)
                {
                    _host = hostPort;
                    _port = 0;
                }
                else
                {
                    _host = hostPort.Substring(0, pos);
                    _port = int.Parse(hostPort.Substring(pos + 1));
                }
            }

            //  (/<url-path>)? (?<query>)? (;<args>)?
            String query, args;
            {
                int pqry = pathQueryArgs.IndexOf('?');
                int parg = pathQueryArgs.IndexOf(';');

                if (pqry != -1 && parg != -1)
                {
                    if (pqry < parg)
                    {
                        _path = pathQueryArgs.Substring(0, pqry);
                        query = pathQueryArgs.Substring(pqry + 1, parg - pqry - 1);
                        args = pathQueryArgs.Substring(parg + 1);
                    }
                    else
                    {
                        _path = pathQueryArgs.Substring(0, pqry);
                        query = pathQueryArgs.Substring(pqry + 1);
                        args = pathQueryArgs.Substring(parg + 1, pqry - parg - 1);
                    }
                }
                else if (pqry != -1)
                {
                    _path = pathQueryArgs.Substring(0, pqry);
                    query = pathQueryArgs.Substring(pqry + 1);
                    args = String.Empty;
                }
                else if (parg != -1)
                {
                    _path = pathQueryArgs.Substring(0, parg);
                    query = String.Empty;
                    args = pathQueryArgs.Substring(parg + 1);
                }
                else
                {
                    _path = pathQueryArgs;
                    query = String.Empty;
                    args = String.Empty;
                }
            }

            // (?<query>)? (;<args>)?
            {
                parseQuery(query);
                parseQuery(args);
            }

        }

        // name=value&...  |  name&...
        // name=value, ...  |  name, ...
        private void parseQuery(String query)
        {
            if (query.Length != 0)
                foreach (String entry in query.Split(SEPARATOR))
                {
                    String name, value;
                    int pos = entry.IndexOf('=');
                    if (pos == -1)
                    {
                        name = entry;
                        value = "true";
                    }
                    else
                    {
                        name = entry.Substring(0, pos).Trim();
                        value = entry.Substring(pos + 1).Trim();
                    }

                    _query[name] = value;
                }
        }

        private String decode(String s)
        {
            if (s.IndexOf('%') == -1)
                return s;

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < s.Length; ++i)
            {
                char c = s[i];
                if (c == '%')
                {
                    sb.Append((char)(toHex(s[++i]) * 16 + toHex(s[++i])));
                }
                else
                {
                    sb.Append(c);
                }
            }

            return sb.ToString();
        }

        private int toHex(char c)
        {
            if (c <= '9')
                return c - '0';
            else if (c <= 'F')
                return c - 'A' + 10;
            else
                return c - 'a' + 10;
        }

        private void addProtocol(String protocol)
        {
            int length = _protocols.Length;
            String[] oprotocols = _protocols;
            _protocols = new string[length + 1];

            _protocols[0] = protocol;
            Array.Copy(oprotocols, 0, _protocols, 1, length);
            _url = String.Format("{0}:{1}", protocol, _url);
        }

        private bool hasProtocol(String protocol)
        {
            foreach (String p in _protocols)
                if (p.Equals(protocol))
                    return true;
            return false;
        }

        #endregion

        #region Overrides

        public override String ToString()
        {
            /*StringBuilder sb = new StringBuilder();
            foreach (string protocol in _protocols)
            {
                sb.Append(protocol).Append(':');
            }

            sb.Append("//");
            if (_user.Length != 0)
            {
                sb.Append(_user);
                if (_path.Length != 0)
                    sb.Append(':').Append(_password);
                sb.Append('@');
            }

            sb.Append(_host);
            if (_port != 0)
                sb.Append(':').Append(_port);

            if (_path.Length != 0)
                sb.Append(_path);

            if (_query.Count != 0)
            {
                int index = 0;
                foreach (KeyValuePair<String, String> pair in _query)
                {
                    if (index++ == 0)
                        sb.Append('?');
                    else
                        sb.Append('&');

                    sb.Append(pair.Key).Append('=').Append(pair.Value);
                }
            }

            return sb.ToString();*/
            return _url;
        }

        #endregion
    }
}
